#!/usr/bin/python
# -*- coding:utf-8 -*-
import re
import arrow
import uuid

from django.utils import timezone
from django.conf import settings
from rest_framework import serializers as dj_ser
from rest_framework_mongoengine import serializers as mg_ser
from mongoengine.errors import DoesNotExist, ValidationError
from rest_framework_simplejwt.serializers import (
    TokenObtainPairSerializer,
    TokenRefreshSerializer,
    RefreshToken,
    api_settings
)
from utilities.log import audit_log
from utilities.regex import PHONE_RE
from utilities.djconsole import djconsole
from utilities.redis import captcha_redis as redis_db
from utilities.custom_exception import MaxMultipleDevicesError
from apps.account_manager.models import User, UserGroups, Permissions, Tokens, OutUrls, OutputUrlChoices
from apps.account_manager.display import Tips
from apps.target.serializer import SerializerTargetGroup
from models.warn import WebReceivers
from .error_message import (
    USERNAME_ERROR,
    PASSWORD_ERROR,
    PERMISSION_TAG_ERROR,
    DESC_ERROR,
    PERMISSION_PATH_ERROR,
    PERMISSION_NAME_ERROR,
    USER_GROUP_ERROR,
)
from utilities.enums import LogModuleName, LogTypeName, AuditLogAction
from .account_utils import cache_login_count_data, cache_user_token, delete_token


class PermissionSerializer(mg_ser.DocumentSerializer):

    path = dj_ser.CharField(
        allow_blank=False,
        required=True,
        max_length=127,
        error_messages={
            'invalid': PERMISSION_PATH_ERROR,
            'max_length': PERMISSION_PATH_ERROR,
            'blank': PERMISSION_PATH_ERROR,
            'required': PERMISSION_PATH_ERROR,
        },
    )
    name = dj_ser.CharField(
        allow_blank=False,
        required=True,
        max_length=127,
        error_messages={
            'invalid': PERMISSION_NAME_ERROR,
            'max_length': PERMISSION_NAME_ERROR,
            'blank': PERMISSION_NAME_ERROR,
            'required': PERMISSION_NAME_ERROR,
        },
    )
    tag = dj_ser.CharField(
        required=True,
        allow_blank=False,
    )
    desc = dj_ser.CharField(
        required=False,
        allow_blank=True,
        max_length=255,
        error_messages={
            'invalid': DESC_ERROR,
            'max_length': DESC_ERROR,
        },
    )
    method = dj_ser.ChoiceField(
        required=True,
        choices=("POST", "GET", "PUT", "DELETE"),
        error_messages={
            "invalid_choice": "访问方法只能选择('POST', 'GET', 'PUT', 'DELETE')中一个",
            "required": "必须选择一个访问方式, ('POST', 'GET', 'PUT', 'DELETE')",
        },
    )
    created_at = dj_ser.DateTimeField(read_only=True)
    updated_at = dj_ser.DateTimeField(read_only=True)

    def validate_path(self, path):
        path = path.lower()
        if not re.fullmatch(r"^/[-_a-z0-9/]*", path):
            raise dj_ser.ValidationError(PERMISSION_PATH_ERROR)
        return path

    def validate_tag(self, tag):
        if not re.fullmatch(r"\w{1,63}", tag):
            raise dj_ser.ValidationError(PERMISSION_TAG_ERROR)
        return tag

    def check_permission_dup(self, path, method):
        try:
            self.Meta.model.objects.get(path=path, method=method)
        except DoesNotExist:
            pass
        else:
            raise dj_ser.ValidationError(f"权限 {path}:{method} 重复")

    def check_name_dup(self, name):
        try:
            self.Meta.model.objects.get(name=name)
        except DoesNotExist:
            pass
        else:
            raise dj_ser.ValidationError(f"权限名 '{name}' 重复")

    def create(self, validated_data):
        path = validated_data["path"]
        method = validated_data["method"]
        name = validated_data["name"]
        self.check_permission_dup(path, method)
        self.check_name_dup(name)
        permission = self.Meta.model(
            created_at=timezone.now(), updated_at=timezone.now(), **validated_data
        )
        permission.save()
        log_data = {
            "module": LogModuleName.user_management.value,
            "action": AuditLogAction.add.value,
            "type": LogTypeName.permission.value,
            "desc": {
                "detail": f"增加业务权限, "
                f"{validated_data.get('tag')}|{name}|{path}|{method}|{validated_data.get('desc', '')}"
            },
        }
        request = self.context["request"]
        audit_log(request, **log_data)
        return permission

    def update(self, instance, validated_data):
        old_name = instance.name
        path = validated_data.get("path", instance.path)
        name = validated_data.get("name", old_name)
        method = validated_data.get("method", instance.method)
        if not (path == instance.path and method == instance.method):
            self.check_permission_dup(path, method)
        if not name != instance.name:
            self.check_name_dup(name)
        for k, v in validated_data.items():
            if hasattr(instance, k):
                setattr(instance, k, v)
        instance.updated_at = timezone.now()
        instance.save()
        log_data = {
            "module": LogModuleName.user_management.value,
            "action": AuditLogAction.up.value,
            "type": LogTypeName.permission.value,
            "desc": {
                "detail": f"修改业务权限{old_name} >> {instance.tag}|{name}|{path}|{method}|{instance.desc}"
            },
        }
        request = self.context["request"]
        audit_log(request, **log_data)
        return instance

    class Meta:
        model = Permissions


class UserGroupSerializer(mg_ser.DocumentSerializer):

    group = dj_ser.CharField(
        max_length=63,
        required=True,
        allow_blank=False,
        error_messages={
            'invalid': USER_GROUP_ERROR,
            'max_length': USER_GROUP_ERROR,
            'blank': USER_GROUP_ERROR,
            'required': USER_GROUP_ERROR,
        },
    )
    permissions = dj_ser.ListField(
        child=dj_ser.CharField(
            max_length=127,
            allow_blank=False,
            error_messages={
                'invalid': PERMISSION_NAME_ERROR,
                'max_length': PERMISSION_NAME_ERROR,
                'blank': PERMISSION_NAME_ERROR,
            },
        ),
        allow_empty=True,
        error_messages={
            "not_a_list": "用户组权限需要是一个列表",
        },
    )
    created_at = dj_ser.DateTimeField(read_only=True)
    updated_at = dj_ser.DateTimeField(read_only=True)
    desc = dj_ser.CharField(
        required=False,
        allow_blank=True,
        max_length=255,
        error_messages={
            'invalid': DESC_ERROR,
            'max_length': DESC_ERROR,
        },
    )

    def validate_group(self, group):
        if not re.fullmatch(r"\w{1,63}", group):
            raise dj_ser.ValidationError(USER_GROUP_ERROR)
        return group

    def validate_permissions(self, permissions):
        if not permissions:
            return []
        per_objs = []
        for per in permissions:
            try:
                per_obj = Permissions.objects.get(name=per.strip())
            except DoesNotExist:
                raise dj_ser.ValidationError(f"所选权限 '{per}' 不存在")
            else:
                per_objs.append(per_obj)
        return per_objs

    def check_group_dup(self, group):
        try:
            self.Meta.model.objects.get(group=group)
        except DoesNotExist:
            pass
        else:
            raise dj_ser.ValidationError(f"用户组 '{group}' 重复")

    def create(self, validated_data):
        group = validated_data.get("group")
        validated_data.setdefault("permissions", [])
        self.check_group_dup(group)
        user_group = self.Meta.model(
            created_at=timezone.now(), updated_at=timezone.now(), **validated_data
        )
        user_group.save()
        log_data = {
            "module": LogModuleName.user_management.value,
            "action": AuditLogAction.add.value,
            "type": LogTypeName.user_group.value,
            "desc": {
                "detail": f"增加用户组 {group}| {[p.__str__() for p  in validated_data.get('permissions', [])]}"
                f"|{validated_data.get('desc', '')}"
            },
        }
        request = self.context["request"]
        audit_log(request, **log_data)
        return user_group

    def update(self, instance, validated_data):
        old_group = instance.group
        group = validated_data.get("group", old_group)
        if group != instance.group:
            self.check_group_dup(group)
        for k, v in validated_data.items():
            if hasattr(instance, k):
                setattr(instance, k, v)
        instance.updated_at = timezone.now()
        instance.save()
        log_data = {
            "module": LogModuleName.user_management.value,
            "action": AuditLogAction.up.value,
            "type": LogTypeName.user_group.value,
            "desc": {
                "detail": f"修改用户组{old_group} >> {group}|"
                f"{[p.__str__() for p  in instance.permissions]}|{instance.desc or ''}"
            },
        }
        request = self.context["request"]
        audit_log(request, **log_data)
        return instance

    class Meta:
        model = UserGroups


class InPutApiTokenSerializer(mg_ser.DocumentSerializer):

    token_id = dj_ser.CharField(read_only=True)
    token = dj_ser.CharField(read_only=True)
    username = dj_ser.SerializerMethodField()
    created_at = dj_ser.DateTimeField(read_only=True)
    updated_at = dj_ser.DateTimeField(read_only=True)
    is_active = dj_ser.BooleanField(default=True)
    expired_time = dj_ser.DateTimeField(required=True)
    desc = dj_ser.CharField(max_length=200, allow_blank=True, required=False)

    @staticmethod
    def get_username(obj):
        user = User.objects.filter(user_id=obj.user_id).first()
        username = user.username if user else ""
        return username

    @staticmethod
    def validate_expired_time(expire_time):
        if arrow.get(expire_time) <= arrow.now():
            raise mg_ser.serializers.ValidationError(Tips().get(2))
        return expire_time

    def check_license_info(self):
        license_info = djconsole.get_license_detail(self.context["request"])
        return "api" in license_info.get("modules", [])

    def validate(self, attrs):
        if not self.check_license_info():
            raise mg_ser.serializers.ValidationError(Tips().get(3))
        return attrs

    def check_url_count(self):
        user_id = self.context["request"].user.user_id
        if self.Meta.model.objects.filter(user_id=user_id):
            raise mg_ser.serializers.ValidationError(Tips().get(4))

    def create(self, validated_data):
        self.check_url_count()
        instance = self.Meta.model(
            token=uuid.uuid4().hex,
            user_id=self.context["request"].user.user_id,
            is_active=validated_data["is_active"],
            expired_time=validated_data["expired_time"],
            desc=validated_data.get("desc", ""),
        ).save()
        log_data = {
            "module": "account_manager",
            "action": "add",
            "type": "api_token",
            "desc": {
                "detail": f"新增授权token, token: {instance.token}| 过期时间: {instance.expired_time} \
                | 是否启用: {instance.is_active}| 备注: {instance.desc}"
            },
        }
        audit_log(self.context["request"], **log_data)
        return instance

    def update(self, instance, validated_data):
        instance.desc = validated_data.get("desc", instance.desc)
        instance.is_active = validated_data.get("is_active", instance.is_active)
        instance.expired_time = validated_data.get("expired_time", instance.expired_time)
        instance.save()
        log_data = {
            "module": "account_manager",
            "action": "up",
            "type": "api_token",
            "desc": {
                "detail": f"修改授权token为, 过期时间: {instance.expired_time} \
                | 是否启用: {instance.is_active}| 备注: {instance.desc}"
            },
        }
        audit_log(self.context["request"], **log_data)
        return instance

    class Meta:
        model = Tokens
        fields = ["id", "token_id", "token", "desc", "username", "created_at",
                  "updated_at", "is_active", "expired_time"]


class OutputUrlSerializer(InPutApiTokenSerializer):

    url_id = dj_ser.CharField(read_only=True)
    username = dj_ser.SerializerMethodField()
    desc = dj_ser.CharField(required=False, allow_blank=True, max_length=200)
    created_at = dj_ser.DateTimeField(read_only=True)
    updated_at = dj_ser.DateTimeField(read_only=True)
    url = dj_ser.URLField(required=True, allow_blank=False, max_length=512)
    auth_type = dj_ser.ChoiceField(choices=OutputUrlChoices.AUTH_TYPE, required=True)
    add_to = dj_ser.ChoiceField(choices=OutputUrlChoices.ADD_TO, default="header")
    method = dj_ser.ChoiceField(choices=OutputUrlChoices.METHOD, required=True)
    auth_key = dj_ser.CharField(required=True, allow_blank=True, max_length=256)
    auth_value = dj_ser.CharField(required=True, allow_blank=True, max_length=1024)
    is_active = dj_ser.BooleanField(default=True)
    expired_time = dj_ser.DateTimeField(required=True)

    def check_url_count(self):
        user_id = self.context["request"].user.user_id
        if self.Meta.model.objects.filter(user_id=user_id):
            raise mg_ser.serializers.ValidationError(Tips().get(5))

    def create(self, validated_data):
        self.check_url_count()
        instance = self.Meta.model(
            url=validated_data["url"],
            user_id=self.context["request"].user.user_id,
            is_active=validated_data["is_active"],
            auth_type=validated_data["auth_type"],
            add_to=validated_data["add_to"],
            auth_key=validated_data["auth_key"],
            auth_value=validated_data["auth_value"],
            method=validated_data["method"],
            expired_time=validated_data["expired_time"],
            desc=validated_data.get("desc", "")
        ).save()
        log_data = {
            "module": "account_manager",
            "action": "add",
            "type": "api_url",
            "desc": {
                "detail": f"新增推送URL, URL: {instance.url}| 过期时间: {instance.expired_time} \
                | 是否启用: {instance.is_active}| 备注: {instance.desc} | 访问方法: {instance.method} | \
                认证KEY/用户名: {instance.auth_key} | 认证值/密码: {instance.auth_value}"
            },
        }
        audit_log(self.context["request"], **log_data)
        return instance

    def update(self, instance, validated_data):
        instance.url = validated_data.get("url", instance.url)
        instance.is_active = validated_data.get("is_active", instance.is_active)
        instance.auth_type = validated_data.get("auth_type", instance.auth_type)
        instance.auth_key = validated_data.get("auth_key", instance.auth_key)
        instance.auth_value = validated_data.get("auth_value", instance.auth_value)
        instance.method = validated_data.get("method", instance.method)
        instance.expired_time = validated_data.get("expired_time", instance.expired_time)
        instance.desc = validated_data.get("desc", instance.desc)
        instance.save()
        log_data = {
            "module": "account_manager",
            "action": "up",
            "type": "api_url",
            "desc": {
                "detail": f"修复推送URL为, URL: {instance.url}| 过期时间: {instance.expired_time} \
                | 是否启用: {instance.is_active}| 备注: {instance.desc} | 访问方法: {instance.method} | \
                认证KEY/用户名: {instance.auth_key} | 认证值/密码: {instance.auth_value}"
            },
        }
        audit_log(self.context["request"], **log_data)
        return instance

    class Meta:
        model = OutUrls
        fields = ["id", "url_id", "username", "created_at", "updated_at", "url", "method", "desc",
                  "auth_type", "add_to", "auth_key", "auth_value", "is_active", "expired_time"]


class UserSerializer(mg_ser.DocumentSerializer):
    class Meta:
        model = User

    username = dj_ser.CharField(required=False)
    password = dj_ser.CharField(write_only=True)
    # pre_login_info = dj_ser.DictField(required=False)
    # cur_login_info = dj_ser.DictField(required=False)
    group = dj_ser.ListField(child=dj_ser.CharField(), required=False)
    product = dj_ser.CharField(required=False)
    is_staff = dj_ser.BooleanField(default=False)  # 能否登录管理平台
    is_superuser = dj_ser.BooleanField(default=False)  # 是否是超级管理员
    is_freeze = dj_ser.BooleanField(default=False)  # 账号冻结标志
    is_active = dj_ser.BooleanField(default=True)  # 账号是否启用
    expired_time = dj_ser.DateTimeField(required=False)  # 账号过期时间
    balance = dj_ser.IntegerField(default=0)  # 账户余额，威胁情报查询次数
    # 地址信息
    province = dj_ser.CharField(required=False)  # 省
    city = dj_ser.CharField(required=False)  # 市
    address = dj_ser.CharField(required=False)  # 具体地址
    # 电话信息
    phone = dj_ser.CharField(required=False)  # 电话
    is_active_phone = dj_ser.BooleanField(default=False)
    # 邮箱信息
    email = dj_ser.EmailField(required=False)
    is_active_email = dj_ser.BooleanField(default=False)
    # 身份证信息
    idcard_num = dj_ser.CharField(required=False)  # 身份证号
    real_name = dj_ser.CharField(required=False)
    is_active_identity = dj_ser.BooleanField(
        default=False
    )  # 实名身份认证状态，True-已验证 False-未验证
    # 其他信息
    qq = dj_ser.CharField(required=False)
    wx = dj_ser.CharField(required=False)  # 绑定微信
    pw_timestamp = dj_ser.DateTimeField(required=False)  # 密码到期时间
    desc = dj_ser.CharField(max_length=255, allow_blank=True)  # 备注信息
    warn_settings = dj_ser.DictField(required=False)  # 告警信息
    mfa = dj_ser.CharField(write_only=True)  # 用户是否绑定MFA
    profile = dj_ser.DictField(required=False)  # 扩展信息
    parent_username = dj_ser.SerializerMethodField()

    def get_parent_username(self, obj):
        parent = self.Meta.model.objects.filter(user_id=obj.parent_id).first()

        # default
        if not parent:
            return "admin"
        return parent.username


class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):

    def validate(self, attrs):
        data = super().validate(attrs)
        refresh = self.get_token(self.user)
        result = {
            "refresh": str(refresh),
            "token": str(refresh.access_token),
            "username": self.user.username,
        }
        data["data"] = result
        data["status"] = "success"
        data["user_group"] = [g.group for g in self.user.group]
        data.pop("refresh", "")
        data.pop("access", "")
        cache_user_token(access_token=result["token"], refresh_token=result["refresh"])
        flag, message = cache_login_count_data(self.user.id, result["refresh"])
        if not flag:
            raise MaxMultipleDevicesError()
        return data


class CustomTokenRefreshSerializer(TokenRefreshSerializer):

    def validate(self, attrs):
        last_refresh = attrs["refresh"]
        if redis_db.get(f"websoc_black_refresh_{last_refresh}"):
            raise dj_ser.ValidationError("this refresh can not be used")
        delete_token(token=last_refresh, token_type="refresh")
        refresh = RefreshToken(last_refresh)
        user_id = refresh.get("user_id", "")
        if (not user_id) or (not User.objects.filter(id=user_id)):
            return {"status": "fail", "errcode": -1, "data": {}}
        data = {"token": str(refresh.access_token)}
        if api_settings.ROTATE_REFRESH_TOKENS:
            if api_settings.BLACKLIST_AFTER_ROTATION:
                try:
                    refresh.blacklist()
                except AttributeError:
                    pass
            refresh.set_jti()
            refresh.set_exp()
            data['refresh'] = str(refresh)
        cache_login_count_data(user_id, data.get('refresh'))
        cache_user_token(access_token=data["token"], refresh_token=data.get('refresh'))
        return {"status": "success", "data": data}


class SerializerUserInfo(mg_ser.DocumentSerializer):
    class Meta:
        model = User
        fields = [
            'real_name',
            'email',
            'phone',
            'department',
            'group'
        ]

    real_name = mg_ser.serializers.CharField(default='', max_length=64)
    email = mg_ser.serializers.EmailField(default='')
    phone = mg_ser.serializers.CharField(required=False, allow_blank=True)
    department = mg_ser.serializers.CharField(required=False, allow_blank=True, max_length=64)
    group = mg_ser.serializers.ListField(child=dj_ser.CharField(), required=False, max_length=16)

    @staticmethod
    def validate_phone(phone):
        if phone and not re.match(PHONE_RE, phone):
            raise mg_ser.serializers.ValidationError(Tips().get(0))

        return phone

    @staticmethod
    def update_default_contact_email(validated_data):
        WebReceivers.objects(receiver_type=0).update(receiver_email=validated_data.get('email'))

    @staticmethod
    def validate_group(groups):
        return RegisterSerializer.validate_group(groups)


class SerializerUserDetail(SerializerUserInfo):
    class Meta(SerializerUserInfo.Meta):
        fields = [
            *SerializerUserInfo.Meta.fields,
            'expired_time',
            'limit_targets'
        ]

    expired_time = mg_ser.serializers.DateTimeField()
    limit_targets = mg_ser.serializers.ListField(required=False, max_length=256)
    group = mg_ser.serializers.ListField(child=dj_ser.CharField(), required=False, max_length=16, read_only=True)

    @staticmethod
    def validate_limit_targets(limit_targets):
        for target_url in limit_targets:
            if target_url == '*':
                continue
            SerializerTargetGroup.s_validate_target_url(target_url)
        return limit_targets

    @staticmethod
    def validate_expired_time(expire_time):
        if arrow.get(expire_time) <= arrow.utcnow():
            raise mg_ser.serializers.ValidationError(Tips().get(1))
        return expire_time


class RegisterSerializer(SerializerUserDetail):
    class Meta(SerializerUserDetail.Meta):
        fields = [
            *SerializerUserDetail.Meta.fields,
            'password',
            'group',
            'desc',
            'username',
            'parent_id',
            'is_superuser'
        ]

    username = dj_ser.CharField(
        min_length=4,
        max_length=127,
        required=True,
        allow_blank=False,
        error_messages={
            'invalid': USERNAME_ERROR,
            'blank': USERNAME_ERROR,
            'max_length': USERNAME_ERROR,
            'min_length': USERNAME_ERROR,
            'required': USERNAME_ERROR,
        },
    )
    password = dj_ser.CharField(
        min_length=8,
        max_length=127,
        required=True,
        allow_blank=False,
        error_messages={
            'invalid': PASSWORD_ERROR,
            'blank': PASSWORD_ERROR,
            'max_length': PASSWORD_ERROR,
            'min_length': PASSWORD_ERROR,
            'required': PASSWORD_ERROR,
        },
    )
    group = dj_ser.ListField(
        child=dj_ser.CharField(
            allow_blank=False,
            max_length=63,
            error_messages={
                "invalid": USER_GROUP_ERROR,
                "blank": USER_GROUP_ERROR,
                "max_length": USER_GROUP_ERROR,
            },
        ),
        allow_empty=True,
        error_messages={
            'not_a_list': "用户分组应该是一个列表",
        },
    )
    is_superuser = dj_ser.BooleanField(default=False)

    # balance = dj_ser.IntegerField(
    #     min_value=-1,
    #     required=False,
    #     error_messages={
    #         "invalid": "威胁情报查询次数为整数",
    #         "min_value": "威胁情报查询最小设置值为-1， 当值为-1表示不限制次数",
    #     },
    # )
    desc = dj_ser.CharField(
        required=False,
        allow_blank=True,
        max_length=255,
        error_messages={
            'invalid': DESC_ERROR,
            'max_length': DESC_ERROR,
        },
    )

    parent_id = dj_ser.CharField(allow_blank=True, required=False)

    def validate_username(self, value):
        return value.strip().lower()

    @staticmethod
    def validate_group(groups):
        res_groups = []
        for group in groups:
            try:
                group_obj = UserGroups.objects.get(group=group)
            except DoesNotExist:
                raise dj_ser.ValidationError(f"选择的用户分组 '{group}' 不存在")
            else:
                res_groups.append(group_obj)
        return res_groups

    def create(self, validated_data):
        groups = validated_data.get("group") or getattr(
            settings, "DEFAULT_USER_GROUP", []
        )
        if groups and all([isinstance(p, str) for p in groups]):
            _groups = []
            for group in groups:
                try:
                    group = UserGroups.objects.get(group=group)
                except DoesNotExist:
                    raise dj_ser.ValidationError(f"所选用户角色: {group} 不存在")
                else:
                    _groups.append(group)
            validated_data["group"] = _groups
        try:
            user = User.create_user(**validated_data)
        except ValidationError as e:
            raise dj_ser.ValidationError(str(e))
        return user
